﻿# ---------------------------------------------------------- #
# Active Directory Certificate Services (ADCS) Configuration #
# ---------------------------------------------------------- #
param([string]$virtualMachineService, [string]$virtualMachineName)

# Return host IP
function Get-HostIP($host)
{
    return (Get-AzureVM -ServiceName ($subscription.virtualMachines.virtualMachine | ? {$_.name -eq $host}).service -Name $host).IpAddress
}

Write-Information "Virtual machine '$virtualMachineName' executing Active Directory Certificate Services '$($script.type)' configuration ... " -NoNewLine $true
$session = Get-VirtualMachineInstancePowerShellSession -Subscription $subscription -VirtualMachineName $virtualMachineName -ForceDomainLogin ($script.forceDomainLogin -eq $true) -AllowNetworkConnections
switch ($script.type)
{
	"Install"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$roles)
            Import-Module ServerManager
            $r = $roles.Split(",")
            # Install Certification Authority
            if ($r.IndexOf("CA") -ne -1) { Add-WindowsFeature Adcs-Cert-Authority -IncludeManagementTools -WarningAction Ignore | Out-Null }
            # Install Certificate Enrollment Policy Web Service
            if ($r.IndexOf("CEPWS") -ne -1) { Add-WindowsFeature Adcs-Enroll-Web-Pol -WarningAction Ignore | Out-Null }
            # Install Certificate Enrollment Web Service
            if ($r.IndexOf("CEWS") -ne -1) { Add-WindowsFeature Adcs-Enroll-Web-Svc -WarningAction Ignore | Out-Null }
            # Install Certification Authority Web Enrollment
            if ($r.IndexOf("CAWE") -ne -1) { Add-WindowsFeature Adcs-Web-Enrollment -WarningAction Ignore | Out-Null }
            # Install Network Device Enrollment Service
            if ($r.IndexOf("NDES") -ne -1) { Add-WindowsFeature Adcs-Device-Enrollment -WarningAction Ignore | Out-Null }
            # Install Online Responder
            if ($r.IndexOf("OR") -ne -1) { Add-WindowsFeature Adcs-Online-Cert -WarningAction Ignore | Out-Null }
		} -ArgumentList $script.roles
	}
	"Root CA"
	{
		Send-RemoteFile -Source "$env:dp0\Files\$($script.policy)" -Destination "C:\Windows\CAPolicy.inf" -Session $session
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$rootName, [string]$cryptoProvider, [string]$keyLength, [string]$hashAlgorithm, [string]$validityYears, [string]$url, [string]$domainDN)
            # Configure Standalone Root CA
            Install-AdcsCertificationAuthority -CAType StandaloneRootCA -CACommonName $rootName `
                -CryptoProviderName $cryptoProvider -KeyLength $keyLength -HashAlgorithmName $hashAlgorithm `
                -ValidityPeriod Years -ValidityPeriodUnits $validityYears -Force -WarningAction Ignore | Out-Null
            # Configure CDP Extension
            CertUtil –setreg CA\CRLPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%3%8.crl\n2:http://$url/%3%8.crl" | Out-Null
            # Configure AIA Extension
            CertUtil –setreg CA\CACertPublicationURLs "2:http://$url/%3%4.crt" | Out-Null
            # Configure CA properties
            CertUtil –setreg CA\CRLPeriodUnits 26 | Out-Null
            CertUtil –setreg CA\CRLPeriod "Weeks" | Out-Null
            CertUtil –setreg CA\CRLDeltaPeriodUnits 0 | Out-Null
            CertUtil –setreg CA\CRLDeltaPeriod "Days" | Out-Null
            CertUtil –setreg CA\CRLOverlapPeriodUnits 12 | Out-Null
            CertUtil –setreg CA\CRLOverlapPeriod "Hours" | Out-Null
            CertUtil –setreg CA\ValidityPeriodUnits $validityYears | Out-Null
            CertUtil –setreg CA\ValidityPeriod "Years" | Out-Null
            CertUtil –setreg CA\DSConfigDN "CN=Configuration,$domainDN" | Out-Null
            Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\$rootName\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy" -Name RequestDisposition -Value 1
            # Restart service (to apply the property changes)
            Restart-Service CertSvc
            do { Start-Sleep 3 } while ((Get-Service CertSvc).Status -ne "Running")
            $name = (Get-Item "C:\Windows\System32\CertSrv\CertEnroll\*.crt").Name
            Rename-Item "C:\Windows\System32\CertSrv\CertEnroll\$name" "C:\Windows\System32\CertSrv\CertEnroll\$rootName.crt"
            # Generate Standalone Root CA certificate CRL
            CertUtil -CRL | Out-Null
		} -ArgumentList $script.rootName, $script.cryptoProvider, $script.keyLength, $script.hashAlgorithm, $script.validityYears, $script.url, $script.domainDN
	}
	"Issuing CA (Part 1 / 3)"
	{
		Send-RemoteFile -Source "$env:dp0\Files\$($script.policy)" -Destination "C:\Windows\CAPolicy.inf" -Session $session
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$rootServer, [string]$issuingName, [string]$cryptoProvider, [string]$keyLength, [string]$hashAlgorithm)
            # Configure Enterprise Subordinate CA
            Install-AdcsCertificationAuthority -CAType EnterpriseSubordinateCA -CACommonName $issuingName `
                -CryptoProviderName $cryptoProvider -KeyLength $keyLength -HashAlgorithmName $hashAlgorithm `
                -OutputCertRequestFile "C:\$issuingName.req" -OverwriteExistingCAinDS -OverwriteExistingKey -Force -WarningAction Ignore | Out-Null
            # Copy Enterprise Subordinate CA certificate request
            Move-Item "C:\$issuingName.req" "\\$rootServer\C$"
		} -ArgumentList (Get-HostIP $script.rootServer), $script.issuingName, $script.cryptoProvider, $script.keyLength, $script.hashAlgorithm
        # Troubleshooting:
        # - Active Directory Sites and Services: Show Services Node, Services -> Public Key Services
        # - PKIView.msc
        # - CertUtil -DCInfo Verify
        # - CertUtil -DCInfo deleteBad
	}
	"Issuing CA (Part 2 / 3)"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$rootName, [string]$issuingName)
            # Issue Enterprise Subordinate CA certificate
            $result = CertReq –Submit -config "$Env:COMPUTERNAME\$rootName" "C:\$issuingName.req" "C:\$issuingName.cer"
            if (!$result[0].StartsWith("RequestId: ")) { throw "Certificate request submission failed." }
            Remove-Item "C:\$issuingName.req"
            Remove-Item "C:\$issuingName.rsp"
		} -ArgumentList $script.rootName, $script.issuingName
	}
	"Issuing CA (Part 3 / 3)"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$rootServer, [string]$rootName, [string]$issuingName, [string]$validityYears, [string]$url)
            if (!(Test-Path -LiteralPath "C:\Certificates" -PathType Container))
            {
                New-Item -Path "C:\Certificates" –Type Directory | Out-Null
            }
            # Publish Standalone Root CA certificate to Active Directory
            CertUtil –dsPublish –f "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName.crt" RootCA | Out-Null
            CertUtil –addstore –f root "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName.crt" | Out-Null
            CertUtil –addstore –f root "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName.crl" | Out-Null
            Copy-Item "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName.crt" "C:\Certificates\$rootName.crt"
            Copy-Item "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName.crl" "C:\Certificates\$rootName.crl"
            # Complete Enterprise Subordinate CA configuration
            CertUtil -installCert "\\$rootServer\C$\$issuingName.cer" | Out-Null
            Start-Service CertSvc
            do { Start-Sleep 3 } while ((Get-Service CertSvc).Status -ne "Running")
            Remove-Item "\\$rootServer\C$\$issuingName.cer"
            # Configure CDP Extension
            CertUtil –setreg CA\CRLPublicationURLs "65:C:\Windows\System32\CertSrv\CertEnroll\%3%8%9.crl\n2:http://$url/%3%8%9.crl" | Out-Null
            # Configure AIA Extension
            CertUtil –setreg CA\CACertPublicationURLs "2:http://$url/%3%4.crt" | Out-Null
            # Configure CA properties
            CertUtil -setreg CA\CRLPeriodUnits 2 | Out-Null
            CertUtil -setreg CA\CRLPeriod "Weeks" | Out-Null
            CertUtil -setreg CA\CRLDeltaPeriodUnits 1 | Out-Null
            CertUtil -setreg CA\CRLDeltaPeriod "Days" | Out-Null
            CertUtil -setreg CA\CRLOverlapPeriodUnits 12 | Out-Null
            CertUtil -setreg CA\CRLOverlapPeriod "Hours" | Out-Null
            CertUtil -setreg CA\ValidityPeriodUnits $validityYears | Out-Null
            CertUtil -setreg CA\ValidityPeriod "Years" | Out-Null
            # Configure Root CA Properties -> Policy Module -> Windows Default -> Request Handling
            Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\$issuingName\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy" -Name RequestDisposition -Value 1
            # Restart service (to apply the property changes)
            Restart-Service CertSvc
            do { Start-Sleep 3 } while ((Get-Service CertSvc).Status -ne "Running")
            $name = (Get-Item "C:\Windows\System32\CertSrv\CertEnroll\*.crt").Name
            Rename-Item "C:\Windows\System32\CertSrv\CertEnroll\$name" "C:\Windows\System32\CertSrv\CertEnroll\$issuingName.crt"
            Copy-Item "C:\Windows\System32\CertSrv\CertEnroll\$issuingName.crt" "C:\Certificates\$issuingName.crt"
            Copy-Item "C:\Windows\System32\CertSrv\CertEnroll\$issuingName.crl" "C:\Certificates\$issuingName.crl"
            # Generate Standalone Root CA certificate CRL
            CertUtil -CRL | Out-Null
		} -ArgumentList (Get-HostIP $script.rootServer), $script.rootName, $script.issuingName, $script.validityYears, $script.url
	}
	"CAWE"
	{
		Invoke-Command -Session $session -ScriptBlock {
            # Configure Certification Authority Web Enrollment
            Install-AdcsWebEnrollment -Force | Out-Null
		}
	}
	"OR"
	{
		Invoke-Command -Session $session -ScriptBlock {
            # Configure Online Responder
            Install-AdcsOnlineResponder -Force | Out-Null
		}
	}
	"CEWS"
	{
		Invoke-Command -Session $session -ScriptBlock {
            # Configure Certificate Enrollment Web Service
            Install-AdcsEnrollmentWebService -Force | Out-Null
		}
	}
	"CEPWS"
	{
		Invoke-Command -Session $session -ScriptBlock {
            # Configure Certificate Enrollment Policy Web Service
            Install-AdcsEnrollmentPolicyWebService -Force | Out-Null
		}
	}
	"NDES"
	{
		Invoke-Command -Session $session -ScriptBlock {
            param([string]$userName, [string]$password)
            # Configure Network Device Enrollment Service
            $group = [ADSI]"WinNT://$Env:COMPUTERNAME/IIS_IUSRS,group"
            try
            {
                $group.psbase.Invoke("Add", ([ADSI]"WinNT://$Env:USERDOMAIN/$($userName.Substring($userName.IndexOf("\") + 1))").path)
            }
            catch { }
            Install-AdcsNetworkDeviceEnrollmentService -ServiceAccountName $userName -ServiceAccountPassword (ConvertTo-SecureString $password -AsPlainText -Force) -Force | Out-Null
		} -ArgumentList $script.userName, $script.password
	}
	"Templates"
	{
        # Enable / disable Certificate templates
        foreach ($template in $script.templates.template)
        {
		    Invoke-Command -Session $session -ScriptBlock {
		        param([string]$template, [string]$enable)
                if ($enable -eq $true)
                {
                    Add-CATemplate -Name $template -Force
                }
                else
                {
                    Remove-CATemplate -Name $template -Force
                }
		    } -ArgumentList $template.name, $template.enable
        }
    }
	"PKI Web"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$rootServer, [string]$rootName, [string]$issuingName, [string]$virtualDirectory)
            Import-Module ServerManager
            Import-Module WebAdministration
            # Install Web Server Role
            Add-WindowsFeature Web-Server -IncludeManagementTools -WarningAction Ignore | Out-Null
            # Create PKI Virtual Directory
            if (!(Test-Path -LiteralPath "C:\inetpub\$virtualDirectory" -PathType Container))
            {
                New-Item -Path "C:\inetpub\$virtualDirectory" –Type Directory | Out-Null
            }
            if ((Get-WebVirtualDirectory -Site "Default Web Site" -Name "$virtualDirectory") -eq $null)
            {
                New-WebVirtualDirectory -Name "$virtualDirectory" -Site "Default Web Site" -PhysicalPath "C:\inetpub\$virtualDirectory" | Out-Null
            }
            Icacls "C:\inetpub\$virtualDirectory" /grant:r "IIS AppPool\DefaultAppPool:(OI)(CI)(RX)" | Out-Null
            Icacls "C:\inetpub\$virtualDirectory" /grant:r "Cert Publishers:(OI)(CI)(M)" | Out-Null
            Set-WebConfigurationProperty -Filter "/system.webServer/security/requestFiltering" -Name "allowDoubleEscaping" -Value true -PSPath "IIS:\Sites\Default Web Site\$virtualDirectory"
            # Populate PKI folder
            Write-Output "CPS Statement" | Out-File "C:\inetpub\$virtualDirectory\cps.txt"
            Copy-Item "\\$rootServer\C$\Windows\System32\CertSrv\CertEnroll\$rootName*" "C:\inetpub\$virtualDirectory"
            Copy-Item "C:\Windows\System32\CertSrv\CertEnroll\$issuingName*" "C:\inetpub\$virtualDirectory"
		} -ArgumentList (Get-HostIP $script.rootServer), $script.rootName, $script.issuingName, $script.virtualDirectory
	}
	"Generate Certificate"
	{
		Send-RemoteFile -Source "$env:dp0\Files\$($script.configuration)" -Destination "C:\$($script.certificateFile).inf" -Session $session
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$issuingName, [string]$certificateFile, [string]$password)
            Move-Item "C:\$certificateFile.inf" "C:\Certificates\$certificateFile.inf" -Force
            # Generate new certificate
            CertReq -New -f -q "C:\Certificates\$certificateFile.inf" "C:\Certificates\$certificateFile.req" | Out-Null
            $result = CertReq –Submit -f -q -config "$Env:COMPUTERNAME\$issuingName" "C:\Certificates\$certificateFile.req" "C:\Certificates\$certificateFile.crt"
            if (!($result[0].StartsWith("RequestId: ")) -or ($result[2].IndexOf("(Issued)") -eq -1)) { throw "Certificate request submission failed." }
            $request = $result[0].Substring($result[0].IndexOf(": ") + 2)
            Write-Output $request | Out-File "C:\Certificates\$certificateFile.req" -Force
            # Export generated certificate
            CertReq -Retrieve -f -q -config "$Env:COMPUTERNAME\$issuingName" $request "C:\Certificates\$certificateFile.crt" | Out-Null
            CertReq -Accept "C:\Certificates\$certificateFile.crt"
            $thumbprint = (Get-PfxCertificate "C:\Certificates\$certificateFile.crt").Thumbprint
            CertUtil -exportPFX -f -p "$password" My $thumbprint "C:\Certificates\$certificateFile.pfx" "NoChain,NoRoot" | Out-Null
		} -ArgumentList $script.issuingName, $script.certificateFile, $script.password
	}
	"Import Certificate"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$issuingServer, [string]$certificateFile, [string]$password)
            CertUtil -importPFX -p "$password" My "\\$issuingServer\C$\Certificates\$certificateFile.pfx" "NoExport" | Out-Null
		} -ArgumentList (Get-HostIP $script.issuingServer), $script.certificateFile, $script.password
        # Verify certificates
        # - CertUtil -v -URLCache *
        # - CertUtil -v -urlfetch -verify "Certificate.crt"
        # - CertUtil -v -verifystore "My" "Certificate Name"
        # - CertUtil –URL "Certificate.crt"
	}
	"Import CA"
	{
		Invoke-Command -Session $session -ScriptBlock {
		    param([string]$issuingServer, [string]$certificateStore, [string]$certificateFile)
            CertUtil –addstore –f $certificateStore "\\$issuingServer\C$\Certificates\$certificateFile.crt" | Out-Null
            CertUtil –addstore –f $certificateStore "\\$issuingServer\C$\Certificates\$certificateFile.crl" | Out-Null
		} -ArgumentList (Get-HostIP $script.issuingServer), $script.certificateStore, $script.certificateFile
	}
	default
	{
		throw "Active Directory Certificate Services type '$($script.type)' is not supported."
	}
}
Remove-PSSession $session
Write-Text "done."